package java核心及工具类;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;

import lombok.Data;
import 基础.Score;
/*

public class hello {
   public static void main(String[] args) {
      int[] scores = new int[] { 88, 77, 51, 66 };
      Integer in=Integer.valueOf(10);
      Score s = new Score(scores,in);
      s.printScores();
      scores[2] = 99;
      in=20;
      System.out.println("--"+Arrays.toString(s.getScores())+s.getIn());
      s.printScores();
  }
}

@Data
class Score {
  private int[] scores;
  private Integer in;
  public Score(int[] scores,Integer in) {
      this.scores = scores;
      this.in=in;
  }
  public int[] getScores() {
     return this.scores;
 }

 public Integer getIn() {
     return this.in;
 }
  public void printScores() {
      System.out.println(Arrays.toString(scores)+in);
  }
}



为什么大家都在说String是不可变的？

【String类的声明】
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    //The value is used for character storage.
    private final char value[];
    
    ...
    ...
   
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

java中String声明是final类型，说明String不可继承，String里面核心存储值的value是一个char[]数组也使用了final修饰，说明value的引用地址是不改变。

【final关键字的作用】
使用final修饰的变量，编译器不允许我们做修改的，举例说明：

public class String的不可变性 {
	public static void main(String[] args) {
        final char[] value = {'a','a','a'};
        char[] value2 = {'b','b','b'};
        //value = value2;//cannot assign a value to final variable

	    //上面value = value2这一行会报错，无法为final修饰的变量赋值，意思就是使用final修饰的变量的引用不能被更改。但是引用不能被更改不代表
	    //不能改变里面的值。如下说所示：

        //final char[] value = {'a', 'a', 'a'};
        System.out.println(value);//aaa
        value[2] = 'b';//将索引位置为2的修改为b
        System.out.println(value);//aab

	//【String的不变性】
	//这里简单的例子测试一下String的不可变性

        String test1 = new String("aaa");
        StringBuilder test2 = new StringBuilder("aaa");
        System.out.println("输出原始的test1：" + test1);//输出原始的test1：aaa
        System.out.println("输出原始的test2：" + test2.toString());//输出原始的test2：aaa

        //追加字符串之后
        String afaddStr = addString(test1);
        StringBuilder afaddStrBuild =  addStringBuilder(test2);
        System.out.println("输出原始的test1：" + test1);//输出原始的test1：aaa   test1在一顿操作之后还保留了原来的样子
        System.out.println("输出原始的test2：" + test2.toString());//输出原始的test2：aaabbb
    
        String key1 = new String("aaa");
        StringBuilder key2 = new StringBuilder("aaa");
        HashSet set = new HashSet<>();
        set.add(key1);
        set.add(key2);
        System.out.println("原始存储的set值："+ set.toString());
        //模拟业务操作
        String afaddStr1 = addString(key1);//不改变set
        System.out.println(afaddStr1);//aaabbb
        System.out.println("addString(key1) 业务执行后set值："+ set.toString());//addString(key1) 业务执行后set值：[aaa, aaa]
        StringBuilder afaddStrBuild1 =  addStringBuilder(key2);//改变set
        //模拟业务操作 end
        System.out.println("addStringBuilder(key2) 业务执行后set值："+ set.toString());//addStringBuilder(key2) 业务执行后set值：[aaa, aaabbb]
        
	}
    //向字符串中添加bbb
    static String addString(String str) {
        str = str + "bbb";
        return str;
    }

    //向字符串中追加bbb
    static StringBuilder addStringBuilder(StringBuilder str) {
        str.append("bbb");
        return str;
    }
    
    
}

test1在一顿操作之后还保留了原来的样子，但是test2已经被改变了，其实String这种不可变性我们经常无意间使用，最常见的使用场景就是HashSet的值。看如下示例：

    @Test
    public void testString2() {
        String key1 = new String("aaa");
        StringBuilder key2 = new StringBuilder("aaa");
        HashSet set = new HashSet<>();
        set.add(key1);
        set.add(key2);
        System.out.println("原始存储的set值："+ set.toString());
        //模拟业务操作
        String afaddStr = addString(key1);
        StringBuilder afaddStrBuild =  addStringBuilder(key2);
        //模拟业务操作 end
        System.out.println("业务执行后set值："+ set.toString());
    }
    //向字符串中添加bbb
    public String addString(String str) {
        str = str + "bbb";
        return str;
    }
    //向字符串中追加bbb
    public StringBuilder addStringBuilder(StringBuilder str) {
        str.append("bbb");
        return str;
    }


如果使用StringBuilder存储则我们无意间的业务操作会改变Set中的值，这可能引起不必要的麻烦。

【String的不变性有哪些好处】
只有当字符串是不可变的，字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间，因为不同的字符串变量都指向池中的同一个字符串。如果字符串是可变的，
那么String interning将不能实现(String interning是指对不同的字符串仅仅只保存一个，即不会保存多个相同的字符串)，因为这样的话，如果变量改变了它的值，
那么其它指向这个值的变量的值也会一起改变。
如果字符串是可变的，那么会引起很严重的安全问题。譬如，数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接，或者在socket编程中，主机名和端口都是以字
符串的形式传入。 因为字符串是不可变的，所以它的值是不可改变的，否则黑客们可以钻到空子，改变字符串指向的对象的值，造成安全漏洞。
因为字符串是不可变的，所以是多线程安全的，同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
因为字符串是不可变的，所以在它创建的时候hashcode就被缓存了，不需要重新计算。这就使得字符串很适合作为Map中的键，字符串的处理速度要快过其它的键对象。这就
是HashMap中的键往往都使用字符串。
【String真的是绝对不可变吗】
看如下例子：

    @Test
    public void testString3() throws IllegalAccessException, NoSuchFieldException {
        String strObj = new String("aaa");
        System.out.println("反射执行前字符串：" + strObj);
        System.out.println("反射执行前的hash值：" + strObj.hashCode());
        Field field = strObj.getClass().getDeclaredField("value");
        field.setAccessible(true);
        char[] value = (char[]) field.get(strObj);
        value[2] = 'b';
        System.out.println("反射执行后字符串：" + strObj);
        System.out.println("反射执行后的hash值：" + strObj.hashCode());
    }

打印结果如下：

反射执行前字符串：aaa
反射执行前的hash值：96321
反射执行后字符串：aab
反射执行后的hash值：96321

说明通过反射我们是可以修改String的值的

【被 final 修饰的成员变量有几个特点】

如果修饰的是实例成员变量，则需要在构造函数执行完毕前完成对成员变量的初始化。如果是静态成员变量，则需要在类初始化完毕前完成初始化。
变量的值不能变。如果修饰的是引用类型，值代表的就是对象中存储的内存地址；如果是基本数据类型，则变量的值不能改变。
value 是被 final 修饰的、实例的、引用类型的成员变量。就意味着，需要在创建 String 对象时对数组进行初始化。且完成初始化后，对象的内存地址不能被改变。

象的内存地址不能被改变。

public class StringTest2 {
    private final char[] value;
    
    public StringTest2(){
        value = new char[]{1,2,3};
    }
    
    public void update(){
        value[2] = 100;
    }
}
虽然 value 的内存地址无法改变，但根据上面例子看来数组中的值是可以改变的。但设计 String 的工程师 
1.将 value 数组私有化(private)，
2.没有对外提供任何修改 value 数组元素的方法( 没有 setter 方法)，
3.在源码中也没有任一地方对 value 数组的值进行了修改。

例如 String.replace 方法，实际不修改 value 数组，而是将 value 数组复制一份，在新的数组上修改后，将新数组返回。测试代码如下：

String str = "test";
String replace = str.replace('t', 'f');
System.out.println(replace);
System.out.println(str);

//output
fesf
test

加上文章开头说的，设计者用 final 修饰 String 类，使 String 类不能被继承，不能拥有子类。 也就不能通过继承的方式实现多态，改变 String 中方法的实
现。我们通过下面的例子了解

public class Son{
    @Override
    public String toString() {
        return "哈哈哈";
    }
}

public void sout(String str){
    System.out.println(str);
}

我们假设 Son 类是 String 的子类，当调用下方 sout 方法时，需要传入 String 类型的对象。了解多态的同学知道，多态就是父类的引用指向子类的对象。我们可以利用
多态，向 sout 方法中传入 Son 的实例，那么结果就是 “哈哈哈” 而非输出 String 对象的值了。

可见设计 String 的工程师是多么的用心良苦，为了让我们知道，String 这个类应该是不可变的(immutable)。

为了避免 String 中的方法被扩展 / 修改，为了保证 String 的不可变性，所以将 String 用 final 修饰以表明 String 不可变。这也就回答了文章开头的第一个问
题。下面我们来解答第二个问题：为什么把 String 设计成不可变，不可变有什么好处。
3.为什么把 String 设计成不可变
安全

我们可以通过以下例子去理解不可变的安全性。

不可变类的实例作为 HashMap 的 key 值
*/
class Student{
    private String name;
    private Integer id;

    public Student(String name, Integer id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return Objects.equals(getName(), student.getName()) &&
                Objects.equals(getId(), student.getId());
    }

    

	@Override
    public int hashCode() {
        return Objects.hash(getName(), getId());
    }
}
public class String的不可变性 {
	public static void main(String[] args) {
        HashMap<String,Integer> test1 = new HashMap<>();
        HashMap<Student,Integer> test2 = new HashMap<>();

        test1.put("1",1);
        test1.put("2",1);

        Student student1 = new Student("thd",1);
        Student student2 = new Student("djw",2);
        test2.put(student1,1);
        test2.put(student2,2);
        student2.setName("thd");
        student2.setId(1);

        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
        System.out.println(student1.equals(student2));
        System.out.println(test2.get(student2));
	}
}

//3559762
//3559762
//true
//1
/*

当 HashMap 中的 key 值为可变类时，如果按照以上的操作，将 student2 的信息改变为与 student1 相同后。通过 hashMap.get 方法获取 value 值，导致信息
不匹配错误。(所以在开发中经常使用不可变字符串作为 HashMap 的 key 值)。相反的，不可变类的实例存入 HashMap 中，不会再被外部通过任何方式修改，也就保
证了 key 的唯一性。





【字符串常量池的实现】
在Java中，我们通常有两种方式创建字符串对象
1.一种是通过字符串字面量方式创建，如：String str="string"
2.另外一种就是通过 new 方式去创建，如：String str= new String("string"); 
而两者区别就在于通过字符串字面量的方式创建时，JVM 会现在字符串池中检查字符串内容是否已经存在，如果存在就会直接返回对应的引用，而不是再次分配内存进行创建，
如果不存在就会分配在内存中创建的同时将字符串数据缓存在字符串池中，便于重用。正是是由于字符串的不可变，同样的字符串内容可以让 JVM 可以减少额外的内存分配
操作，直接使用在字符串池中字符串对象即可，对性能提升和内存节省都大有好处。
关于字符串池，这里稍微简单介绍一下：Java 的字符串池属于 JVM 专门给指定的特殊内存区域，用来存储字符串字面量。在 Java 7 之前，分配于 JVM 的方法区内，
属于常量池的一部分；而 Java7 之后字符串池被移至堆内存进行管理，这样的好处就是允许被 JVM 进行垃圾回收操作，将未被引用的字符串所占内存即使回收，以此节省内存。

【Hashcode 缓存】
字符串作为基础的数据结构，大量地应用在一些集合容器之中，尤其是一些散列集合，在散列集合中，存放元素都要根据对象的 hashCode() 方法来确定元素的位置。由于字
符串 hashcode 属性不会变更，保证了唯一性，使得类似 HashMap，HashSet 等容器才能实现相应的缓存功能。由于 String 的不可变，避免重复计算 hashcode，
只有使用缓存的 hashcode 即可，这样一来大大提高了在散列集合中使用 String 对象的性能。

【线程安全】
在多线程中，只有不变的对象和值是线程安全的，可以在多个线程中共享数据。由于 String 天然的不可变，当一个线程”修改“了字符串的值，只会产生一个新的字符串对象，
不会对其他线程的访问产生副作用，访问的都是同样的字符串数据，不需要任何同步操作。

【安全性】
由于字符串无论在任何 Java 系统中都广泛使用，会用来存储敏感信息，如账号，密码，网络路径，文件处理等场景里，保证字符串 String 类的安全性就尤为重要了，如果
字符串是可变的，容易被篡改，那我们就无法保证使用字符串进行操作时，它是安全的，很有可能出现 SQL 注入，访问危险文件等操作。





*/